package mysqldb

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"gorm.io/plugin/dbresolver"
	"strings"
	"time"
)

type MysqlDB struct {
	Engine *gorm.DB
	Name   string
}

type MysqlSetting struct {
	Name           string        `mapstructure:"name"`
	Type           string        `mapstructure:"type"`
	Write          DbConfig      `mapstructure:"write"`
	Read           DbConfig      `mapstructure:"read"`
	Database       string        `mapstructure:"database"`
	MaxPoolSize    int           `mapstructure:"max_pool_size"`
	MaxIdle        int           `mapstructure:"max_idle"`
	IdleTimeout    time.Duration `mapstructure:"idle_timeout"`
	ConnectTimeout time.Duration `mapstructure:"connect_timeout"`
	DoWithTimeout  time.Duration `mapstructure:"do_with_timeout"`
}

type DbConfig struct {
	Host     string `mapstructure:"host"`
	Username string `mapstructure:"user"`
	Password string `mapstructure:"password"`
}

func InitMysqlDB(setting MysqlSetting) *MysqlDB {
	var errMsg interface{}
	// check setting
	if setting.Write.Host == "" || setting.Write.Username == "" || setting.Write.Password == "" || setting.Database == "" {
		errMsg = "mysql source or username or password or database cannot be null"
		panic(errMsg)
	}
	if setting.MaxPoolSize == 0 {
		setting.MaxPoolSize = 5
	}
	if setting.MaxIdle == 0 {
		setting.MaxIdle = 2
	}
	if int64(setting.ConnectTimeout) == 0 {
		setting.ConnectTimeout = 2 * time.Second
	} else {
		setting.ConnectTimeout *= time.Second
	}
	if int64(setting.DoWithTimeout) == 0 {
		setting.DoWithTimeout = 2 * time.Second
	} else {
		setting.DoWithTimeout *= time.Second
	}
	if int64(setting.IdleTimeout) == 0 {
		setting.IdleTimeout = 60 * time.Second
	} else {
		setting.IdleTimeout *= time.Second
	}

	//连接MYSQL, 获得DB类型实例，用于后面的数据库读写操作。
	//db, err := gorm.Open(mysql.Open("db1_dsn"), &gorm.Config{})
	//db.Use(dbresolver.Register(dbresolver.Config{
	//	// `db2` 作为 sources，`db3`、`db4` 作为 replicas
	//	Sources:  []gorm.Dialector{mysql.Open("db2_dsn")},
	//	Replicas: []gorm.Dialector{mysql.Open("db3_dsn"), mysql.Open("db4_dsn")},
	//	// sources/replicas 负载均衡策略
	//	Policy: dbresolver.RandomPolicy{},
	//}).Register(dbresolver.Config{
	//	// `db1` 作为 sources（DB 的默认连接），对于 `User`、`Address` 使用 `db5` 作为 replicas
	//	Replicas: []gorm.Dialector{mysql.Open("db5_dsn")},
	//}, &User{}, &Address{}).Register(dbresolver.Config{
	//	// `db6`、`db7` 作为 sources，对于 `orders`、`Product` 使用 `db8` 作为 replicas
	//	Sources:  []gorm.Dialector{mysql.Open("db6_dsn"), mysql.Open("db7_dsn")},
	//	Replicas: []gorm.Dialector{mysql.Open("db8_dsn")},
	//}, "orders", &Product{}, "secondary"))
	var err error
	var _db *gorm.DB

	// 设置默认链接，主
	_db, err = gorm.Open(mysql.Open(setting.writeURL()), &gorm.Config{})
	if err != nil {
		errMsg = "连接数据库失败, errno=" + err.Error()
		panic(errMsg)
	}
	//  设置从库
	if setting.Read.Host != "" {
		replicaConfig := dbresolver.Config{
			Sources:  []gorm.Dialector{mysql.Open(setting.writeURL())},
			Replicas: []gorm.Dialector{mysql.Open(setting.readURL())},
			//sources/replicas 负载均衡策略
			//Policy: dbresolver.RandomPolicy{},
		}
		// 从库连接池
		err = _db.Use(
			dbresolver.Register(replicaConfig).
				SetMaxOpenConns(setting.MaxPoolSize).
				SetMaxIdleConns(setting.MaxIdle).
				SetConnMaxIdleTime(setting.IdleTimeout).
				SetConnMaxLifetime(4 * time.Hour), //连接池里面的连接最大存活时长,比默认数据链接超时时长要小
		)
		if err != nil {
			errMsg = "从库连接池失败, errno=" + err.Error()
			panic(errMsg)
		}
	}
	// 插件设置
	err = _db.Use(MonMysqlPlugin{DBName: setting.Database})
	if err != nil {
		errMsg = "mysqlMonPlugin失败, errno=" + err.Error()
		panic(errMsg)
	}

	sqlDb, _ := _db.DB()
	// gorm底层依赖database/sql
	if sqlDb != nil {
		// 主库连接池
		sqlDb.SetMaxOpenConns(setting.MaxPoolSize)    //设置数据库连接池最大连接数
		sqlDb.SetMaxIdleConns(setting.MaxIdle)        //连接池最大允许的空闲连接数，如果没有sql任务需要执行的连接数大于20，超过的连接会被连接池关闭。
		sqlDb.SetConnMaxIdleTime(setting.IdleTimeout) // SetConnMaxIdleTime 设置了空闲连接的最大存活时间
		sqlDb.SetConnMaxLifetime(4 * time.Hour)       //连接池里面的连接最大存活时长,比默认数据链接超时时长要小
	}

	return &MysqlDB{
		Name:   setting.Name,
		Engine: _db,
	}
}

func (d *MysqlDB) SetLogger(logger logger.Interface) {
	d.Engine.Logger = logger
}

func (d *MysqlDB) Close() {
	sqlDb, _ := d.Engine.DB()
	_ = sqlDb.Close()
}

//timeout是指 建立连接的一个超时时间
//readTimeout是指 I/O 读操作的超时时间
//writeTimeout是指 I/O 写操作的超时时间
func (my *MysqlSetting) makeURL(host, username, passowrd string) string {
	var params = []string{
		"charset=utf8mb4",
		"parseTime=True",
		"loc=Local",
		fmt.Sprintf("timeout=%dms", my.ConnectTimeout.Milliseconds()),
		fmt.Sprintf("readTimeout=%dms", my.DoWithTimeout.Milliseconds()),
		fmt.Sprintf("writeTimeout=%dms", my.DoWithTimeout.Milliseconds()),
	}
	return username + ":" + passowrd + "@(" + host + ")/" + my.Database + "?" + strings.Join(params, "&")
}

func (my *MysqlSetting) readURL() string {
	return my.makeURL(my.Read.Host, my.Read.Username, my.Read.Password)
}

func (my *MysqlSetting) writeURL() string {
	return my.makeURL(my.Write.Host, my.Write.Username, my.Write.Password)
}
